
from direct.showbase.ShowBase import ShowBase
from direct.showbase import DirectObject
from pandac.PandaModules import WindowProperties
from direct.task import Task

#from panda3d.core import CollisionTraverser,CollisionNode
#from panda3d.core import CollisionHandlerQueue,CollisionRay
#from panda3d.core import Point3,Vec3,Vec4,BitMask32
#from panda3d.core import AmbientLight,DirectionalLight,LightAttrib
from panda3d.core import *

from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator

from direct.gui.OnscreenText import OnscreenText
from MainMenu import *
from MapGenerator import *
from AIManager import *
import sys
from Player import *
from Unit import *
from Network import *
from Inputs import *

class Main(ShowBase):
    dragging = False
    canBeDragged = False

    port = 9100

    picked = None
    pickedIndex = -1

    myTurn = True

    IsServer = False
    Connected = False

    GameStarted = False

    def __init__(self):

        self.player1 = Player()
        self.player2 = Player()
        ShowBase.__init__(self)
        self.client = Client()
        self.server = Server()
        self.server.DataRecived = self.Data_Recived
        self.server.ClientConnected = self.Server_ClientConnected
        self.client.ConnectedToServer = self.Client_Connected
        self.client.DataRecived = self.Data_Recived

        self.MouseListener = self.Mouse_StateChanged
        self.KeyboardListener = self.Keyboard_StateChanged
        self.InitKeyboard(self)
        self.InitMouse(self)

        self.setupLights()
        #self.InitCamera()
        self.InitEvents()
        self.InitObjectPicker()

        self.mainMenu = MainMenu()
        self.mainMenu.ShowMainMenu()
        self.mainMenu.StateChanged = self.MainMenu_Events
        #add mouse button 1 handler
        self.MouseListener = EventBase()
        self.KeyboardListener = EventBase()
        self.setupMouseCollision()

    def InitKeyboard(self, target):
        if target != None:
            target.accept('arrow_up', self.KeyboardListener, ['arrow_up'])
            target.accept('arrow_down', self.KeyboardListener, ['arrow_down'])
            target.accept('arrow_left', self.KeyboardListener, ['arrow_left'])
            target.accept('arrow_right', self.KeyboardListener, ['arrow_right'])
            target.accept('arrow_up-repeat', self.KeyboardListener, ['arrow_up-repeat'])
            target.accept('arrow_down-repeat', self.KeyboardListener, ['arrow_down-repeat'])
            target.accept('arrow_left-repeat', self.KeyboardListener, ['arrow_left-repeat'])
            target.accept('arrow_right-repeat', self.KeyboardListener, ['arrow_right-repeat'])
            target.accept('escape', self.KeyboardListener, ['escape'])

    def InitMouse(self, target):
        if target != None:
            target.accept('mouse1', self.onMouseTask)
            target.accept('mouse1-up', self.MouseListener, ['mouse1-up'])
            target.accept('wheel_up', self.MouseListener, ['wheel_up'])
            target.accept('wheel_down', self.MouseListener, ['wheel_down'])
            target.accept('mouse3', self.MouseListener, ['mouse3'])
            target.accept('mouse3_up', self.MouseListener, ['mouse3_up'])

     
    def onMouseTask(self):
        """ """
        try:
            #do we have a mouse
            if (self.mouseWatcherNode.hasMouse() == False):
                return

            #get the mouse position
            mpos = base.mouseWatcherNode.getMouse()

            #Set the position of the ray based on the mouse position

            self.mPickRay.setFromLens(self.camNode, mpos.getX(), mpos.getY())

            #for this small example I will traverse everything, for bigger projects
            #this is probably a bad idea
            self.mPickerTraverser.traverse(self.hex)
            radius = 5
            if (self.mCollisionQue.getNumEntries() > 0):
                self.mCollisionQue.sortEntries()
                entry     = self.mCollisionQue.getEntry(0);
                pickedObj = entry.getIntoNodePath()

                if not pickedObj.isEmpty():
                    #here is how you get the surface collsion
                    pos = entry.getSurfacePoint(self.render)
                    hpr = pickedObj.getHpr()
                    s = pickedObj.getName().split('-')
                    self.pickedNode.show()
                    x = int(s[0])
                    y = int(s[1])
                    self.pickedNode.removeNode()
                    self.pickedNode = render.attachNewNode(GeomNode("Picked"))
                    self.pickedNode.setTransparency(TransparencyAttrib.MAlpha)
                
                    for hexyX in range(-radius, radius):
                        for hexyY in range(-radius, radius):
                            gnode = GeomNode("Picked"+str(hexyX)+str(hexyY))
                            gnode.addGeom(self.clickHex)
                            NP = self.pickedNode.attachNewNode(gnode)
                            NP.setColor(0.0, 0.0, 0.0, 0.5)

                            if hexyX*hexyX + hexyY*hexyY <= radius*radius:
                
                                if x % 2 == 0:
                                    if hexyX % 2 == 0:
                                        NP.setPos(y+hexyY, (hexyX+x)*0.855, self.mapGen.heightTab[(hexyX+x)*128+y+hexyY]+0.02)
                                    else:
                                        NP.setPos(y+hexyY+0.5, (hexyX+x)*0.855, self.mapGen.heightTab[(hexyX+x)*128+y+hexyY]+0.02)
                                else:
                                    if hexyX % 2 != 0:
                                        NP.setPos(y+hexyY, (hexyX+x)*0.855, self.mapGen.heightTab[(hexyX+x)*128+y+hexyY]+0.02)
                                    else:
                                        NP.setPos(y+hexyY+0.5, (hexyX+x)*0.855, self.mapGen.heightTab[(hexyX+x)*128+y+hexyY]+0.02)
                    self.pickedNode.flattenStrong()
        except ValueError:
            print 'wefwefewf'

    def MainMenu_Events(self, e):
        if(e == States.CREATEGAME):
            self.server.Connect(self.port)
            if self.server.Connected:
                self.mainMenu.Hide()
                self.InitTerrain()
                self.player1.DeployUnits("xml/Player1.xml", self.hexMap)
        
                self.player2.DeployUnits("xml/Player2.xml", self.hexMap)
                self.IsServer= True
                self.GameStarted = True
        elif e == States.EXIT:
            sys.exit()
        elif e == States.JOINGAME:
            self.client.Connect(self.mainMenu.serverAddress, self.port)
            if self.client.Connected:
                self.mainMenu.Hide()
                self.InitTerrain()
                self.GameStarted = True
            self.IsServer = False

    def SynchUnits(self, changeTurn=False):
        cmd1 = "{0}{1}".format(Commands.SYNCH, self.GetUnitsCmdSufix(self.player1.Units, self.player1.Site))
        cmd2 = "{0}{1}".format(Commands.SYNCH, self.GetUnitsCmdSufix(self.player2.Units, self.player2.Site, changeTurn))
        
        self.Send(cmd1)
        self.Send(cmd2)    
        
    

    def Send(self, cmd):
        if self.IsServer:
            if self.server.Connected:
                self.server.Send(cmd)                
        else:
            if self.client.Connected:
                self.client.Send(cmd)                      
        
    def Server_ClientConnected(self, e):
        self.SendLoadXml("Player1.xml", "Player2.xml")
        self.myTurn = True
        

    def Client_Connected(self, e):
        self.myTurn = False
        self.GameStarted = True
        
    def Data_Recived(self, e):
        self.Analyze(e)

    def Mouse_StateChanged(self, e):
        pass

    def Keyboard_StateChanged(self, e):
        if e == 'arrow_up' or e == 'arrow_up-repeat':
            self.MoveCameraUp()
        elif e == 'arrow_down' or e == 'arrow_down-repeat':
            self.MoveCameraDown()
        elif e == 'arrow_left' or e == 'arrow_left-repeat':
            self.MoveCameraLeft()
        elif e == 'arrow_right' or e == 'arrow_right-repeat':
            self.MoveCameraRight()

        elif e == 'escape':
            pass

    def setupLights(self):    #This function sets up some default lighting
        ambientLight = AmbientLight( "ambientLight" )
        ambientLight.setColor( Vec4(.8, .8, .8, 1) )
        directionalLight = DirectionalLight( "directionalLight" )
        directionalLight.setDirection( Vec3( 0, 45, -45 ) )
        directionalLight.setColor( Vec4( 0.2, 0.2, 0.2, 1 ) )
        self.render.setLight(render.attachNewNode( directionalLight ) )
        self.render.setLight(render.attachNewNode( ambientLight ) )       


    def Analyze(self, cmd):
        tmp = str.split(cmd, ":")
        if str.startswith(tmp[0], Commands.LOADXML):
           
            self.player1.DeployUnits("xml/{0}".format(tmp[1]), self.hexMap)
            self.player2.DeployUnits("xml/{0}".format(tmp[2]), self.hexMap)
        elif str.startswith(tmp[0], Commands.PING):
            pass
        elif str.startswith(tmp[0], Commands.ACK):
            pass
        elif str.startswith(tmp[0], Commands.BATTLE):
            pass
        elif str.startswith(tmp[0], Commands.START_GAME):
            pass
        elif str.startswith(tmp[0], Commands.STOP_GAME):
            pass
        elif str.startswith(tmp[0], Commands.TIMER):
            pass
        elif str.startswith(tmp[0], Commands.SYNCH):
            n = int(tmp[1])
            site = tmp[3]
            changeTurn = bool(tmp[2])
            
            for i in range(4,n+4):
                unitInfo = str.split(tmp[i], ";")
                index = int(unitInfo[0])
                if site == "Axis":                    
                    #self.player2.Units[index].localName = unitInfo[1]
                    self.player2.Units[index].hex.x = unitInfo[1]
                    self.player2.Units[index].hex.x = unitInfo[2]
                    self.player2.Units[index].size = unitInfo[3]
                else:
                    #self.player1.Units[index].localName = unitInfo[1]
                    self.player1.Units[index].hex.x = unitInfo[1]
                    self.player1.Units[index].hex.x = unitInfo[2]
                    self.player1.Units[index].size = unitInfo[3]
            if site == "Axis":
                self.player2.Update()
            else:
                self.player1.Update()

            if changeTurn:
                self.myTurn = ~self.myTurn

    def PointAtZ(self, z, point, vec):
        return point + vec * ((z-point.getZ()) / vec.getZ())

    def InitEvents(self):
        self.taskMgr.add(self.CameraControl, "CameraControl")

    def InitObjectPicker(self):
        self.objectPickerTraverser = CollisionTraverser()
        self.objectPickerHandler = CollisionHandlerQueue()
        self.objectPickerNode = CollisionNode("mouseRay")
        self.objectPickerNP = self.camera.attachNewNode(self.objectPickerNode)
        #Everything to be picked will use bit 1. This way if we were doing other
        #collision we could seperate it
        self.objectPickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.objectPickerRay = CollisionRay()               #Make our ray
        self.objectPickerNode.addSolid(self.objectPickerRay)      #Add it to the collision node
        #Register the ray as something that can cause collisions
        self.objectPickerTraverser.addCollider(self.objectPickerNP, self.objectPickerHandler)
        #self.picker.showCollisions(render)
        self.objectPickerTraverser.showCollisions(self.render) 

    def InitCamera(self, singleHexSize):
        base.disableMouse()
        #self.camera.setP(-30)
        self.camLens.setFov(45)
        halfsize = self.hexMap.size*singleHexSize/2
        self.camera.setPos(halfsize, -10, 20)
        self.camera.lookAt(halfsize, 10, 0)

    def InitAI(self):
        aiManager = AIManager(self.hexMap)

    def SendLoadXml(self, xml1, xml2):
        self.Send("{0}:{1}:{2}:".format(Commands.LOADXML, xml1, xml2))

          
    def MoveCameraLeft(self):
        if self.GameStarted:
            self.camera.setX(self.camera.getX() - .3)
        if self.camera.getX() < 0:
            self.camera.setX(0)

    def MoveCameraRight(self):
        if self.GameStarted:
            self.camera.setX(self.camera.getX() + .3)
        if self.camera.getX() > 128:
            self.camera.setX(128)

    def MoveCameraUp(self):
        if self.GameStarted:
            self.camera.setY(self.camera.getY() + .3)
        if self.camera.getY() > 120:
            self.camera.setY(120)

    def MoveCameraDown(self):
        if self.GameStarted:
            self.camera.setY(self.camera.getY() - .3)
        if self.camera.getY() < -20:
            self.camera.setY(-20)

    def CameraControl(self, task):
        if base.mouseWatcherNode.hasMouse():
            mousePos = base.mouseWatcherNode.getMouse()

            x = base.mouseWatcherNode.getMouseX()
            y = base.mouseWatcherNode.getMouseY()

            #self.BroadcastMousePostition(x,y)

            self.objectPickerRay.setFromLens(base.camNode, mousePos.getX(), mousePos.getY())

            #if self.dragging is not False:
            #    #Gets the point described by pickerRay.getOrigin(), which is relative to
            #    #camera, relative instead to render
            #    nearPoint = self.render.getRelativePoint(self.camNode, self.objectPickerRay.getOrigin())
            #    #Same thing with the direction of the ray
            #    nearVec = self.render.getRelativeVector(self.camNode, self.objectPickerRay.getDirection())
            #    

            if (self.pickedIndex > -1) and (self.dragging == True):
                nearPoint = self.render.getRelativePoint(self.camera, self.objectPickerRay.getOrigin())
                #Same thing with the direction of the ray
                nearVec = self.render.getRelativeVector(self.camera, self.objectPickerRay.getDirection())
                self.friendUnits[self.pickedIndex].setPos(self.PointAtZ(.5, nearPoint, nearVec))

            #Do the actual collision pass (Do it only on the squares for
            #efficiency purposes)
            self.objectPickerTraverser.traverse(self.render)
            if (self.objectPickerHandler.getNumEntries() > 0) and (self.dragging == False):
                #if we have hit something, sort the hits so that the closest
                #is first, and highlight that node
                self.objectPickerHandler.sortEntries()
                pickedObj = self.objectPickerHandler.getEntry(0).getIntoNodePath()
                if pickedObj.hasNetTag('panda') :
                    self.pickedIndex = int(pickedObj.getNetTag('panda'))
                
                #i = int(self.objectPickerHandler.getEntry(0).getIntoNode().getTag('square'))
                #Set the highlight on the picked square
                #self.squares[i].setColor(HIGHLIGHT)
                #self.hiSq = i
                #self.dragging = True

            #self.txt.setText('Mouse position: X={0} Y={1}'.format(x,y))
            if (x >= .95) and (x <= 1):
               self.MoveCameraRight()
            elif (x <= -.95) and (x >= -1):
                self.MoveCameraLeft()
            
            if (y >= .95) and (y <= 1):
                self.MoveCameraUp()
            elif (y <= -.95) and (y >= -1):
                self.MoveCameraDown()
            print self.camera.getPos()
        return Task.cont

    def GetUnitsCmdSufix(self, units, site, changeTurn = False):
        n = len(units)
        tmp = ":{0}:{1}:{2}:".format(n, changeTurn, site)
        if n > 0:
            for i in range(n):
                tmp = "{0}{1};{2};{3};{4}:".format(tmp, i, units[i].hex.x, units[i].hex.y, units[i].size)
            return tmp
        return None

    def InitTerrain(self):
        self.singleHexSize = 0.855

        """ responsible for loading all models """
        self.node = GeomNode("Terrain")
        self.hex = render.attachNewNode(self.node)
        self.mapGen = MapGenerator()
        self.hexMap = self.mapGen.GenerateMap("gfx/Heightmap.png", self.hex, 0.05, self.singleHexSize) 
        self.hex.setTag('MyTerrain', '2')
        self.clickHex = self.mapGen.CreateHex()
        gnode = GeomNode("PickedNode")
        gnode.addGeom(self.clickHex)
        self.pickedNode = render.attachNewNode(gnode)
        self.pickedNode.hide()
        self.InitCamera(self.singleHexSize)	

    pickss = 0
  

    def setupMouseCollision(self):
        """ """
        #Since we are using collision detection to do picking, we set it up 
        #any other collision detection system with a traverser and a handler
        self.mPickerTraverser = CollisionTraverser()            #Make a traverser
        self.mCollisionQue    = CollisionHandlerQueue()

        #create a collision solid ray to detect against
        self.mPickRay = CollisionRay()
        self.mPickRay.setOrigin(self.camera.getPos(self.render))
        self.mPickRay.setDirection(render.getRelativeVector(camera, Vec3(0,1,0)))

        #create our collison Node to hold the ray
        self.mPickNode = CollisionNode('pickRay')
        self.mPickNode.addSolid(self.mPickRay)

        #Attach that node to the camera since the ray will need to be positioned
        #relative to it, returns a new nodepath		
        #well use the default geometry mask
        #this is inefficent but its for mouse picking only

        self.mPickNP = self.camera.attachNewNode(self.mPickNode)

        #well use what panda calls the "from" node.  This is reall a silly convention
        #but from nodes are nodes that are active, while into nodes are usually passive environments
        #this isnt a hard rule, but following it usually reduces processing

        #Everything to be picked will use bit 1. This way if we were doing other
        #collision we could seperate it, we use bitmasks to determine what we check other objects against
        #if they dont have a bitmask for bit 1 well skip them!
        self.mPickNode.setFromCollideMask(GeomNode.getDefaultCollideMask())

        #Register the ray as something that can cause collisions
        self.mPickerTraverser.addCollider(self.mPickNP, self.mCollisionQue)
        #if you want to show collisions for debugging turn this on
        #self.mPickerTraverser.showCollisions(self.render)
   

class Commands:
    UNITS           =   "*U"
    PING            =   "*P"
    PING_RESPONSE   =   "*+P"
    START_GAME      =   "*R"
    STOP_GAME       =   "*S"
    TIMER           =   "*T"
    BATTLE          =   "*B"
    ACK             =   "*A"
    SYNCH           =   "*C"
    LOADXML         =   "*L"
    

app = Main()
app.run()
